/*
  wificonfig.cpp -  wifi functions class

  Copyright (c) 2014 Luc Lebosse. All rights reserved.

  This code is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This code is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with This code; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "espconfig.h"
#ifdef ARDUINO_ARCH_ESP32
#include <esp_wifi.h>
#endif // ARDUINO_ARCH_ESP32
#ifdef ARDUINO_ARCH_ESP8266
#endif // ARDUINO_ARCH_ESP8266
#include "espwificonfig.h"
#include "espsettings.h"

const uint8_t DEFAULT_IP_VALUE[] = {192, 168, 0, 1};
const uint8_t DEFAULT_MASK_VALUE[] = {255, 255, 255, 0};
#define DEFAULT_GATEWAY_VALUE DEFAULT_IP_VALUE
#define DEFAULT_DNS_VALUE DEFAULT_GATEWAY_VALUE
#define DEFAULT_HOSTNAME "ESPWEB"

bool WiFiConfig::ConnectSTA2AP()
{
    String msg, msg_out;
    uint8_t count = 0;
    uint8_t dot = 0;
    wl_status_t status = WiFi.status();
    log_debug("Connecting");
    while (status != WL_CONNECTED && count < 40)
    {

        switch (status)
        {
        case WL_NO_SSID_AVAIL:
            msg = "No SSID";
            break;
        case WL_CONNECT_FAILED:
            msg = "Connection failed";
            break;
        case WL_CONNECTED:
            break;
        default:
            if ((dot > 3) || (dot == 0))
            {
                dot = 0;
                msg_out = "Connecting";
            }
            msg_out += ".";
            msg = msg_out;
            log_debug("...");
            dot++;
            break;
        }

        delay(500);
        count++;
        status = WiFi.status();
    }
    if (status == WL_CONNECTED)
    {
        log_info("IP:%s", WiFi.localIP().toString().c_str());
    }
    return (status == WL_CONNECTED);
}
/*
 * Start client mode (Station)
 */
bool WiFiConfig::StartSTA()
{
    log_debug("StartSTA");
    if ((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))
    {
        WiFi.softAPdisconnect();
    }
    WiFi.enableAP(false);
    WiFi.enableSTA(true);
    WiFi.mode(WIFI_STA);
#if defined(ARDUINO_ARCH_ESP32)
    esp_wifi_start();
    WiFi.setMinSecurity(WIFI_AUTH_WEP);
    WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
    WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
#endif // ARDUINO_ARCH_ESP32
       // Get parameters for STA
    String SSID = Settings::readString(KEY_STA_SSID);
    String password = Settings::readString(KEY_STA_PASSWORD);
    log_debug("STA connect:SSID:%s,pwd:%s", SSID.c_str(), password.c_str());
    if (WiFi.begin(SSID.c_str(), (password.length() > 0) ? password.c_str() : nullptr))
    {
#if defined(ARDUINO_ARCH_ESP8266)
        WiFi.setSleepMode(WIFI_NONE_SLEEP);
        WiFi.hostname(DEFAULT_HOSTNAME);
#endif // ARDUINO_ARCH_ESP8266
#if defined(ARDUINO_ARCH_ESP32)
        WiFi.setSleep(false);
        WiFi.setHostname(DEFAULT_HOSTNAME);
#endif // ARDUINO_ARCH_ESP32
        return ConnectSTA2AP();
    }
    else
    {
        log_debug("Starting client failed");
        return false;
    }

    return true;
}

/**
 * Setup and start Access point
 */

bool WiFiConfig::StartAP()
{
    if ((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))
    {
        if (WiFi.isConnected())
        {
            WiFi.disconnect();
        }
    }
    if ((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))
    {
        WiFi.softAPdisconnect();
    }
    WiFi.enableAP(true);
    WiFi.enableSTA(false);
    WiFi.mode(WIFI_AP);
    // Set Sleep Mode to none
#if defined(ARDUINO_ARCH_ESP8266)
    WiFi.setSleepMode(WIFI_NONE_SLEEP);
#endif // ARDUINO_ARCH_ESP8266

    String SSID = Settings::readString(KEY_AP_SSID);
    String password = Settings::readString(KEY_AP_PASSWORD);

    IPAddress ip(DEFAULT_IP_VALUE), mask(DEFAULT_MASK_VALUE), gateway(DEFAULT_GATEWAY_VALUE), dns(DEFAULT_DNS_VALUE);
    WiFi.config(ip, gateway, mask, dns);
    // Start AP
    log_debug("Use: %s / %s / %s", ip.toString().c_str(), ip.toString().c_str(), mask.toString().c_str());
    if (!WiFi.softAPConfig(ip, ip, mask))
    {
        log_error("Set IP to AP failed");
    }
    if (WiFi.softAP(SSID.c_str(), (password.length() > 0) ? password.c_str() : nullptr))
    {
        String stmp = "AP SSID: '" + SSID;
        if (password.length() > 0)
        {
            stmp += "' is started and protected by password";
        }
        else
        {
            stmp += " is started not protected by password";
        }
        log_debug("%s", stmp.c_str());
#if defined(ARDUINO_ARCH_ESP32)
        // must be done after starting AP not before
        // https://github.com/espressif/arduino-esp32/issues/4222
        // on some phone 100 is ok but on some other it is not enough so 2000 is ok
        delay(2000);
        // Set static IP

        WiFi.setSleep(false);
        WiFi.softAPsetHostname(DEFAULT_HOSTNAME);
        IPAddress ip = WiFi.softAPIP();
        log_info("IP:%s", ip.toString().c_str());
#endif // ARDUINO_ARCH_ESP32
        return true;
    }
    else
    {
        log_debug("Starting AP failed");
        return false;
    }

    return true;
}

/**
 * begin WiFi setup
 */
bool WiFiConfig::begin(int8_t &espMode)
{
    bool res = false;
    // end();

    int8_t wifiMode = espMode;

    if (wifiMode == WIFI_OFF)
    {
        WiFi.mode(WIFI_OFF);
        return true;
    }
    else if (wifiMode == WIFI_STA)
    {
        log_debug("Starting STA mode");
        res = StartSTA();
        if (res == false)
        {
            log_debug("Start STA fail,start ap");
        }
    }
    else if (wifiMode == WIFI_AP)
    {
        log_debug("Starting AP mode");
        res = StartAP();
    }

    return res;
}

/**
 * End WiFi
 */

void WiFiConfig::end()
{
    // Sanity check
    if ((WiFi.getMode() == WIFI_STA) || (WiFi.getMode() == WIFI_AP_STA))
    {
        if (WiFi.isConnected())
        {
            WiFi.disconnect(true);
        }
    }
    if ((WiFi.getMode() == WIFI_AP) || (WiFi.getMode() == WIFI_AP_STA))
    {
        if (WiFi.isConnected())
        {
            WiFi.softAPdisconnect(true);
        }
    }
    WiFi.mode(WIFI_OFF);
}

/**
 * Handle not critical actions that must be done in sync environement
 */

void WiFiConfig::handle()
{
    // to avoid mixed mode
    if (WiFi.getMode() == WIFI_AP_STA)
    {
        if (WiFi.scanComplete() != WIFI_SCAN_RUNNING)
        {
            WiFi.enableSTA(false);
        }
    }
}
